﻿using log4net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace Albatross.WPF.Common.PubSub {
	/// <summary>
	/// A implementation of the IPublisher interface.  The class is Sealed and it has a default Subscription Type of Subscription&lt;T$gt;.
	/// The class provides a global instance using a static property called Global.
	/// </summary>
	public sealed class Publisher : IPublisher {
		static Lazy<Publisher> _global = new Lazy<Publisher>(() => new Publisher(), true);
		/// <summary>
		/// Provide a Global instance of Publisher class.
		/// </summary>
		public static Publisher Global { get { return _global.Value; } }

		object _sync = new object();
		Dictionary<string, List<ISubscription>> _subscriptions = new Dictionary<string, List<ISubscription>>();
		ILog Log = LogManager.GetLogger(typeof(Publisher));

		/// <summary>
		/// Publish a message with a topic to all subscribers.
		/// </summary>
		/// <param name="sender">The sender of the message</param>
		/// <param name="topic">Message Topic</param>
		/// <param name="msg">The message itself</param>
		public void Publish(object sender, string topic, object msg) {
			List<ISubscription> list = null;
			lock (_sync) {
				if (_subscriptions.ContainsKey(topic)) {
					list = _subscriptions[topic];
				}
			}
			if(list != null){
				foreach (var subscription in list) {
					if (subscription.TypeCheck(msg)) {
						subscription.Invoke(sender, msg);
					}
				}
			}
		}
		
		/// <summary>
		/// Subscribe to an event using the standard Subscription class
		/// </summary>
		/// <typeparam name="T">The message type</typeparam>
		/// <param name="subscriber">The subscriber object</param>
		/// <param name="topic">The message topic</param>
		/// <param name="handler">The message event handler</param>
		/// <returns>The subscription object</returns>
		public ISubscription<T> Subscribe<T>(object subscriber, string topic, MsgReceivedEventHandler<T> handler) {
			ISubscription<T> sub = Subscribe<T, Subscription<T>>(subscriber, topic);
			sub.MsgReceived += handler;
			return sub;
		}
		/// <summary>
		/// Subscribe to an event using a particular Subscription class S.  Type S has to implement ISubscription$lt;T$gt;.  The class are expected to have a default constructor.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="S"></typeparam>
		/// <param name="subscriber"></param>
		/// <param name="topic"></param>
		/// <returns></returns>
		public ISubscription<T> Subscribe<T, S>(object subscriber, string topic) where S : ISubscription<T>, new() {
			List<ISubscription> list = null;
			lock (_sync) {
				if (_subscriptions.ContainsKey(topic)) {
					list = _subscriptions[topic];
				} else {
					list = new List<ISubscription>();
					_subscriptions.Add(topic, list);
				}
				ISubscription<T> subscription = Activator.CreateInstance<S>();
				subscription.Init(this, subscriber, topic);
				list.Add(subscription);
				return subscription;
			}
		}
		/// <summary>
		/// Unsubscribe a particular subscription
		/// </summary>
		/// <param name="sub">the subscription class instance</param>
		public void Unsubscribe(ISubscription sub) {
			List<ISubscription> list = null;
			lock (_sync) {
				if (_subscriptions.ContainsKey(sub.Topic)) {
					list = _subscriptions[sub.Topic];
					for (int i = list.Count - 1; i >= 0; i--) {
						if(object.ReferenceEquals(sub, list[i])){
							list.RemoveAt(i);
						}
					}
					if (list.Count == 0) { _subscriptions.Remove(sub.Topic); }
				}
			}
		}
		/// <summary>
		/// Unsubscribe all subscriptions for a particular subscriber.
		/// </summary>
		/// <param name="subscriber"></param>
		public void UnsubscribeAll(object subscriber) {
			lock (_sync) {
				List<string> emptyList = new List<string>();
				foreach (KeyValuePair<string, List<ISubscription>> pair in _subscriptions) {
					for (int i = pair.Value.Count - 1; i >= 0; i--) {
						if (pair.Value[i].Subscriber == subscriber) {
							pair.Value.RemoveAt(i);
						}
					}
					if (pair.Value.Count == 0) {
						emptyList.Add(pair.Key);
					}
				}
				foreach (string key in emptyList) { _subscriptions.Remove(key); }
			}
		}
		/// <summary>
		/// Clear all subscriptions
		/// </summary>
		public void Clear() {
			lock (_sync) {
				//whoever holds the subscriptions should dispose them itself!
				_subscriptions.Clear();
			}
		}
	}
}
